/**
 * ### 防抖函数类型签名；
 * @template A 函数参数类型；
 * @template R 函数返回值类型；
 */
// export type DebouncedFunc<A extends any[], R> = {
//   (...args: A): R | undefined;
//   cancel: () => void;
// };

/**
 * ### 创建一个防抖函数，确保在最后一次调用后，目标函数只会在指定的延迟时间后执行;
 * #### 防抖函数可以防止某个函数被频繁调用，例如:
 * 1、用户输入框实时搜索；减少请求次数；
 * 2、滚动事件；
 * 3、表单验证；缩小验证次数；
 * 4、窗口调整大小等场景；防止调整过程中频繁地重新计算布局；
 * @param func 需要防抖的目标函数。该函数将在延迟时间后执行;
 * @param duration 延迟时间（以毫秒为单位）。在这个时间内，如果再次调用函数，将重新计时;
 * @param immediate 是否立即执行；
 * @returns 一个防抖后的函数，该函数包括一个 `cancel` 方法用于清除防抖;
 *
 * @example
 * ```ts
 * const debouncedLog = debounce((message: string) => {
 *    console.log(message);
 *    return message;
 * }, 300);
 *
 * debouncedLog("Hello, world!"); // 不会立即执行
 * debouncedLog.cancle(); // 取消防抖，函数不会执行
 * ```
 */
export function debounce<T extends (...args: any[]) => any>(
  fn: T,
  duration: number,
  immediate = false
) {
  // 定义一个定时器变量，初始值为null
  let timer: ReturnType<typeof setTimeout> | null = null;
  // 定义一个取消函数，用于取消定时器
  const cancel = () => {
    // 如果定时器存在，则清除定时器
    timer && clearTimeout(timer);
    // 将定时器变量置为null
    timer = null;
  };

  // 定义一个防抖函数
  const debounced = function (
    // 传入this参数和参数列表
    this: ThisParameterType<T>,
    ...args: Parameters<T>
  ) {
    // 保存this上下文
    const context = this;
    // 如果定时器存在，则调用取消函数
    timer && cancel();
    // 如果immediate为true，则立即执行func函数
    if (immediate) {
      // 如果定时器不存在，则执行func函数
      if (!timer) {
        fn.apply(context, args);
      }
      // 设置定时器，在duration时间后执行
      timer = setTimeout(() => {
        // 将定时器变量置为null
        timer = null;
      }, duration);
    } else {
      // 设置定时器，在duration时间后执行func函数
      timer = setTimeout(() => {
        fn.apply(context, args);
      }, duration);
    }
  };
  // 将取消函数添加到debounced函数上
  (debounced as any).cancel = cancel;
  // 返回debounced函数，并添加取消函数
  return debounced as T & { cancel: () => void };
}

/**
 * ### 导出一个函数，该函数接受一个函数和持续时间作为参数，并返回一个新的函数
 * #### 应用场景：
 * 1、滚动事件；
 * 2、鼠标挪动事件；
 * 3、动画成果；
 * @param fn 需要节流的函数
 * @param duration 节流的持续时间，单位为毫秒
 * @returns 返回一个通过节流解决的函数
 */
export function throttle<T extends (...args: any[]) => any>(
  // T是一个泛型，表示传入的函数类型
  fn: T,
  // 持续时间，表示函数执行的最小间隔时间
  duration: number
) {
  // 记录上一次函数执行的时间
  let lastCall: number | null = null;
  // 返回一个新的函数
  return function (
    // this参数，表示函数执行时的上下文
    this: ThisParameterType<T>,
    // 参数列表，表示函数执行时的参数
    ...args: Parameters<T>
  ): ReturnType<T> | void {
    // 获取当前时间
    const now = Date.now();
    // 如果上一次函数执行的时间不存在，或者当前时间与上一次函数执行的时间间隔大于等于持续时间，则执行函数
    if (!lastCall || now - lastCall >= duration) {
      // 更新上一次函数执行的时间
      lastCall = now;
      // 执行函数，并返回结果
      return fn.apply(this, args);
    }
  } as T;
}

function exhaustivityCheck<T>(_: T): void {
  throw new Error("Unhandled discriminated union member: " + _);
}
